package com.northpool.operator.http.covert;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.northpool.operator.annotation.InputParam;
import com.northpool.operator.annotation.InputParams;
import com.northpool.operator.annotation.OperatorInfo;
import com.northpool.operator.common.algorithm.NodeDslRepresenter;
import com.northpool.operator.common.algorithm.OperatorTestBuild;
import com.northpool.operator.common.algorithm.TestParamBuilder;
import com.northpool.operator.common.minio.MinIOUtil;
import com.northpool.operator.common.util.XmlParser;
import com.northpool.operator.http.bean.AppMetadata;
import com.northpool.operator.http.bean.ExpParamBean;
import com.northpool.operator.http.bean.HttpOperatorBean;
import com.northpool.operator.http.bean.ResultApiBean;
import com.northpool.operator.http.bean.StartApiBean;
import com.northpool.operator.param.ParamTool;
import com.northpool.operator.type.TypeEnum;
import com.northpool.operator.utils.StpOperatorHelper;
import com.northpool.operator.yaml.InputParameter;
import com.northpool.operator.yaml.NodeDef;
import com.northpool.operator.yaml.NodeDsl;
import com.northpool.operator.yaml.OutputParameter;
import com.northpool.operator.yaml.ParamEnum;
import com.northpool.operator.yaml.Spec;
import io.minio.Result;
import io.minio.messages.Item;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@OperatorInfo(
        ref = "CovertJianmuNodeDefOperator",
        name = "HTTP算子描述生成节点描述",
        description = "扫描对象存储中版本库的文件,生成流程节点秒描述文件"
)
public class CovertJianmuNodeDefOperator extends StpOperatorHelper {

    private static DumperOptions options = new DumperOptions();
    private static Yaml yaml;

    private static String organization;
    private static String s3Endpoint;
    private static String bucket;
    private static String runtimeVersion;
    private static Map<String, String> orgMap = new HashMap<>();

    static {
        orgMap.put("piesat", "航天宏图");
        orgMap.put("supermap", "超图");

        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // 使用块风格
        options.setPrettyFlow(true); // 使输出更易读
        options.setIndentWithIndicator(true);
        options.setIndicatorIndent(2);
        options.setIndent(2); // 设置缩进为2个空格
        options.setAllowUnicode(true); // 允许Unicode字符

        NodeDslRepresenter nodeDslRepresenter = new NodeDslRepresenter(options);
        yaml = new Yaml(nodeDslRepresenter, options);
    }

    @InputParams({
            @InputParam(ref = "path", name = "minio中对象路径", desc = "minio中对象路径, 如: apps/supermap/"),
            @InputParam(ref = "minioEndpoint", name = "对象存储地址", desc = "http://ip:port"),
            @InputParam(ref = "bucket", name = "桶名称", desc = "桶名称, 如: stp-minio"),
            @InputParam(ref = "accessKey", name = "minio令牌", desc = "minio令牌, 如: northpool", type = TypeEnum.STRING),
            @InputParam(ref = "secretKey", name = "minio密钥", desc = "minio密钥, 如: xxxxxxx", type = TypeEnum.STRING),
            @InputParam(ref = "organization", name = "厂商名称", desc = "厂商名称缩写, 如: piesat"),
            @InputParam(ref = "runtimeVersion", name = "运行环境版本", value = "0.0.3", desc = "HTTP算子运行环境版本，如: 0.0.3"),
    })
    public CovertJianmuNodeDefOperator() {
    }

    public static void main(String[] args) throws Exception {
        // 测试环境
        String s3Endpoint = "http://northpool.work:9000";
        // 正式环境
//        String s3Endpoint = "http://10.21.185.9:9000";
//        String s3Endpoint = "http://192.168.164.45:9000";
        String accessKey = "northpool";
        String secretKey = "piesat@123";

        OperatorTestBuild.execute(CovertJianmuNodeDefOperator.class, TestParamBuilder.aBuilder()
                .param("path", "apps/supermap")
                .param("minioEndpoint", s3Endpoint)
                .param("bucket", "stp-minio")
                .param("accessKey", accessKey)
                .param("secretKey", secretKey)
                .param("organization", "supermap")
                .build());
    }

    @Override
    public void execute(ParamTool params) throws Exception {
        bucket = params.getStr("bucket");
        String path = params.getStr("path");
        s3Endpoint = params.getStr("minioEndpoint");
        String accessKey = params.getStr("accessKey");
        String secretKey = params.getStr("secretKey");
        organization = params.getStr("organization");
        runtimeVersion = params.getStr("runtimeVersion");

        MinIOUtil minioUtil = new MinIOUtil(s3Endpoint, accessKey, secretKey);
        try {
            processDirItem(minioUtil, bucket, path);
        } catch (Exception ex) {
            error("处理失败" + ex.getMessage());
            ex.printStackTrace();
            failedExit();
        }
    }

    private void processDirItem(MinIOUtil minioUtil, String bucket, String path) throws Exception {
        Iterable<Result<Item>> results = minioUtil.listObjects(bucket, path);
        List<String> dirList = new LinkedList<>();
        String iconObjName = null;
        Boolean existsMeta = false;
        // 遍历所有文件列表
        for (Result<Item> result : results) {
            Item item = result.get();
            boolean dir = item.isDir();
            if (dir) {
                dirList.add(item.objectName());
            } else if(item.objectName().endsWith("/STP_META.xml")){
                existsMeta = true;
            } else if(item.objectName().endsWith(".png") || item.objectName().endsWith(".jpg")
                    || item.objectName().endsWith(".jpeg") || item.objectName().endsWith(".svg")) {
                iconObjName = item.objectName();
            }
        }

        if (existsMeta) {
            try {
                processFileItem(minioUtil, bucket, path, iconObjName);
            } catch (Exception ex) {
                error("异常文件：" + path + ",异常信息" + ex.getMessage());
            }
        }

        for (String dirObjName : dirList) {
            processDirItem(minioUtil, bucket, dirObjName);
        }
    }

    private void processFileItem(MinIOUtil minioUtil, String bucket, String parentPath, String iconObjName) throws Exception {
        String metaPath = parentPath + "STP_META.xml";
        InputStream metaInputStream = minioUtil.getObject(bucket, metaPath);
        AppMetadata appMetadata = XmlParser.parserByInputStream(metaInputStream, AppMetadata.class);
        if (appMetadata == null) { // 为空就直接跳过
            error("节点元数据信息描述为空： " + metaPath);
            return  ;
        }

        String packageFile = appMetadata.getPackageFile();
        String documentFile = appMetadata.getDocumentFile();
        String documentLink = "/" + bucket + "/" + parentPath + documentFile;

        InputStream xmlInputStream = minioUtil.getObject(bucket, parentPath + packageFile);
        HttpOperatorBean operatorBean = XmlParser.parserByInputStream(xmlInputStream, HttpOperatorBean.class);
        // 如果有唯一标识，就使用name作为名称，否则就是用xml的文件名作为节点名称
        String nodeName = Objects.nonNull(operatorBean.getRef()) ? operatorBean.getName() : StrUtil.subBefore(packageFile, ".", true);

        NodeDef nodeDef = new NodeDef();
        nodeDef.setName(nodeName);
        nodeDef.setDescription(operatorBean.getDescription());
        nodeDef.setDeprecated(false);
        nodeDef.setType("PROCESS");
        nodeDef.setRef(operatorBean.getRef());
        nodeDef.setOwnerType("ORGANIZATION");
        nodeDef.setOwnerRef(organization);
        nodeDef.setOwnerName(orgMap.containsKey(organization) ? orgMap.get(organization) : organization);
        nodeDef.setCreatorRef(operatorBean.getWriter()); // 后续挂标识
        nodeDef.setCreatorName(operatorBean.getWriter());
        String defaultIcon = "/" + bucket + "/images/node_default.png";
        nodeDef.setIcon(StrUtil.isBlank(iconObjName) ? defaultIcon : s3Endpoint + "/" + bucket + "/" + iconObjName);
        nodeDef.setLatestVersion(operatorBean.getVersion());
        nodeDef.setVisibleType("PUBLIC");
        nodeDef.setSourceLink("");
        nodeDef.setDocumentLink(documentLink);

        NodeDsl nodeDsl = new NodeDsl();
        nodeDsl.setName(nodeName);
        nodeDsl.setDescription(operatorBean.getDescription());
        nodeDsl.setRef(organization + "/" + operatorBean.getRef());
        nodeDsl.setVersion(operatorBean.getVersion());
        nodeDsl.setDocumentLink(documentLink);
        Spec spec = new Spec();

        LinkedHashMap<String, String> apps = new LinkedHashMap<>();
        apps.put("op-http", "piesat/op-http/" + runtimeVersion);
        String xmlPath = parentPath.endsWith("/") ? parentPath.substring(0, parentPath.length() - 1) : parentPath;
        if (xmlPath.startsWith("apps/")) {
            xmlPath = StrUtil.subAfter(xmlPath, "apps/", false);
        }
        if (xmlPath.split("/").length != 3) {
            throw new RuntimeException("应用路径未按照规范的 厂商唯一标识/应用唯一标识/版本号 命名，请规范当前命名: " + xmlPath);
        }
        apps.put("http-xml", xmlPath);
        spec.setApps(apps);
        spec.setRuntimes(MapUtil.of("java", "java/8"));

        spec.setCmd(ListUtil.of("${JAVA_EXE}", "-jar", "${op-http}/op-http-" + runtimeVersion + ".jar", "${http-xml}/" + packageFile));
        nodeDsl.setSpec(spec);
        nodeDsl.setResultFile("result_file");
        LinkedList<InputParameter> inputParameters = new LinkedList<>();
        LinkedList<OutputParameter> outputParameters = new LinkedList<>();

        StartApiBean startApi = operatorBean.getStartApi();
        List<ExpParamBean> startParam = startApi.getStartParam();
        for (ExpParamBean expParamBean : startParam) {
            String inout = expParamBean.getInout();
            if ("IN".equalsIgnoreCase(inout)) {
                InputParameter parameter = new InputParameter();
                parameter.setRef(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getRef() : expParamBean.getName());
                parameter.setName(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getName() : expParamBean.getTitle());
                parameter.setType(getDslTypeStr(expParamBean.getType()));
                parameter.setDescription(expParamBean.getDescription());
                parameter.setRequired(Objects.nonNull(expParamBean.getRequired()) && expParamBean.getRequired()); // 如果xml中未声明，按照非必须处理

                // 枚举值填充
                if (expParamBean.getEnums() != null) {
                    parameter.setEnums(expParamBean.getEnums().stream()
                            .map(p -> new ParamEnum(p.getRef(), p.getName(), p.getDescription()))
                            .collect(Collectors.toList()));
                }
                // 如果是必须的，就没有默认值
                if (!parameter.isRequired()) {
                    parameter.setValue(ObjectUtil.defaultIfBlank(expParamBean.getValue(), ""));
                }
                inputParameters.push(parameter);
            } else if ("OUT".equalsIgnoreCase(inout)) {
                OutputParameter outputParameter = new OutputParameter();
                outputParameter.setRef(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getRef() : expParamBean.getName());
                outputParameter.setName(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getName() : expParamBean.getTitle());
                outputParameter.setDescription(expParamBean.getDescription());
                outputParameter.setType(getDslTypeStr(expParamBean.getType()));
                outputParameter.setRequired(expParamBean.getRequired() == null || expParamBean.getRequired());
                outputParameters.push(outputParameter);
            }
        }
        // 输出参数
        ResultApiBean resultApi = operatorBean.getResultApi();
        if (resultApi != null) {
            List<ExpParamBean> resultParam = Objects.nonNull(resultApi.getResultParam()) ? resultApi.getResultParam() : ListUtil.empty();
            for (ExpParamBean expParamBean : resultParam) {
                if ("OUT".equalsIgnoreCase(expParamBean.getInout())) {
                    OutputParameter outputParameter = new OutputParameter();
                    outputParameter.setRef(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getRef() : expParamBean.getName());
                    outputParameter.setName(StrUtil.isNotBlank(expParamBean.getRef()) ? expParamBean.getName() : expParamBean.getTitle());
                    outputParameter.setDescription(expParamBean.getDescription());
                    outputParameter.setType(getDslTypeStr(expParamBean.getType()));
                    outputParameter.setRequired(expParamBean.getRequired() == null || expParamBean.getRequired());
                    outputParameters.push(outputParameter);
                }
            }
        }
        nodeDsl.setInputParameters(inputParameters);
        nodeDsl.setOutputParameters(outputParameters);

        JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.SortField.getMask();
        SerializeConfig serializeConfig = new SerializeConfig(true);
        // 写入节点描述
        String nodeJsonStr = JSON.toJSONString(nodeDef, serializeConfig);
        JSONObject nodeJsonObj = JSONObject.parseObject(nodeJsonStr, Feature.OrderedField);
        String nodeYaml = yaml.dumpAs(nodeJsonObj, Tag.MAP, DumperOptions.FlowStyle.BLOCK);

        ByteArrayInputStream nodeByteArrayInputStream = new ByteArrayInputStream(nodeYaml.getBytes(StandardCharsets.UTF_8));
        String nodePath = "nodes/" + organization + "/" + operatorBean.getRef() + "/" + "node_def.yml";
        minioUtil.putObject(bucket, nodePath, nodeByteArrayInputStream, "text/plain; charset=utf-8");

        // 写入节点版本描述，去除null值并保持字段顺序
        String jsonString = JSON.toJSONString(nodeDsl, serializeConfig);
        JSONObject jsonObject = JSONObject.parseObject(jsonString, Feature.OrderedField);
        String nodeVersionYaml = yaml.dumpAs(jsonObject, Tag.MAP, DumperOptions.FlowStyle.BLOCK);

        ByteArrayInputStream nodeVersionByteArrayInputStream = new ByteArrayInputStream(nodeVersionYaml.getBytes(StandardCharsets.UTF_8));
        String nodeVersionPath = "nodes/" + organization + "/" + operatorBean.getRef() + "/" + operatorBean.getVersion() + "/" + "node_def.yml";
        minioUtil.putObject(bucket, nodeVersionPath, nodeVersionByteArrayInputStream, "text/plain; charset=utf-8");
        this.log("处理完成：" + nodeVersionPath);
    }

    private static String getDslTypeStr(String type) {
        if ("STRING".equalsIgnoreCase(type)) {
            return "STRING";
        } else if ("BOOLEAN".equalsIgnoreCase(type) || "BOOL".equalsIgnoreCase(type)) {
            return "BOOL";
        } else if ("INTEGER".equalsIgnoreCase(type) || "LONG".equalsIgnoreCase(type) || "NUMBER".equalsIgnoreCase(type)) {
            return "NUMBER";
        } else if ("DB".equalsIgnoreCase(type)) {
            return "DB";
        } else if ("DATABASE".equalsIgnoreCase(type)) {
            return "DATABASE";
        } else if ("DATASOURCE".equalsIgnoreCase(type)) {
            return "DATASOURCE";
        } else if ("ENUM".equalsIgnoreCase(type)) {
            return "ENUM";
        } else {
            throw new RuntimeException("暂不支持的类型：" + type);
        }
    }

}
